/*
 * @Author: your name
 * @Date: 2021-01-13 21:11:51
 * @LastEditTime: 2021-01-25 22:57:48
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \scull\scull.c
 */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h>   /* kmalloc() */
#include <linux/fs.h>     /* everything... */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <asm/uaccess.h> /* copy data betweent kernel/user */
#include "scull.h"

int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS;
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;

module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);

struct scull_dev *scull_devices;

/**
 * @description: 释放scull设备的内存
 * @param: 要清空的scull设备
 * @return 成功返回0
 */
int scull_trim(struct scull_dev *dev)
{
  struct scull_qset *next, *dptr;
  int qset = dev->qset;
  int i;

  for (dptr = dev->data; dptr; dptr = next)
  {
    if (dptr->data)
    {
      for (i = 0; i < qset; i++)
        kfree(dptr->data[i]);
      kfree(dptr->data);
      dptr->data = NULL;
    }
    next = dptr->next;
    kfree(dptr);
  }
  dev->size = 0;
  dev->quantum = scull_quantum;
  dev->qset = scull_qset;
  dev->data = NULL;
  return 0;
}

/**
 * @description: 打开设备
 * @param {structinode} *inode
 * @param {structfile} *filp
 * @return {*}
 */
int scull_open(struct inode *inode, struct file *filp)
{
  struct scull_dev *dev;

  dev = container_of(inode->i_cdev, struct scull_dev, cdev);
  filp->private_data = dev;

  if ((filp->f_flags & O_ACCMODE) == O_WRONLY)
  {
    //可被信号唤醒的休眠
    if (mutex_lock_interruptible(&dev->lock))
      return -ERESTARTSYS;
    scull_trim(dev);
    mutex_unlock(&dev->lock);
  }
  return 0;
}

/**
 * @description: 
 * @param {structinode} *inode
 * @param {structfile} *filp
 * @return {*}
 */
int scull_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/**
 * @description: 定位到第n个量子集
 * @param {*}
 * @return {*}
 */
struct scull_qset *scull_follow(struct scull_dev *dev, int n)
{
  struct scull_qset *qs = dev->data;

  if (!qs)
  {
    qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
    if (qs == NULL)
      return NULL;
    memset(qs, 0, sizeof(struct scull_qset));
  }
  while (n--)
  {
    if (!qs->next)
    {
      qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
      if (qs->next == NULL)
        return NULL;
      memset(qs->next, 0, sizeof(struct scull_qset));
    }
    qs = qs->next;
    continue;
  }
  return qs;
}

/**
 * @description: 读取scull设备的内存数据
 * @param {*}
 * @return {*}
 */
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
                   loff_t *f_pos)
{
  struct scull_dev *dev = filp->private_data;
  struct scull_qset *dptr;
  int quantum = dev->quantum, qset = dev->qset;
  int itemsize = quantum * qset;
  int item, s_pos, q_pos, rest;
  ssize_t retval = 0;

  if (mutex_lock_interruptible(&dev->lock))
    return -ERESTARTSYS;

  //确定读取的长度
  if (*f_pos >= dev->size)
    goto out;
  if (*f_pos + count > dev->size)
    count = dev->size - *f_pos;
  //定位到未读的地址
  item = (long)*f_pos / itemsize;
  rest = (long)*f_pos % itemsize;
  s_pos = rest / quantum;
  q_pos = rest % quantum;

  dptr = scull_follow(dev, item);

  if (dptr == NULL || !dptr->data || !dptr->data[s_pos])
    goto out;

  if (count > quantum - q_pos)
    count = quantum - q_pos;

  if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count))
  {
    retval = -EFAULT;
    goto out;
  }
  *f_pos += count;
  retval = count;

out:
  mutex_unlock(&dev->lock);
  return retval;
}

/**
 * @description: 
 * @param {*}
 * @return {*}
 */
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
                    loff_t *f_pos)
{
  struct scull_dev *dev = filp->private_data;
  struct scull_qset *dptr;
  int quantum = dev->quantum, qset = dev->qset;
  int itemsize = quantum * qset;
  int item, s_pos, q_pos, rest;
  ssize_t retval = 0;

  if (mutex_lock_interruptible(&dev->lock))
    return -ERESTARTSYS;

  item = (long)*f_pos / itemsize;
  rest = (long)*f_pos % itemsize;
  s_pos = rest / quantum;
  q_pos = rest % quantum;

  //定位
  dptr = scull_follow(dev, item);
  if (dptr == NULL)
    goto out;
  if (!dptr->data)
  {
    dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
    if (!dptr->data)
      goto out;
    memset(dptr->data, 0, qset * sizeof(char *));
  }
  if (!dptr->data[s_pos])
  {
    dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
    if (!dptr->data[s_pos])
      goto out;
  }
  if (count > quantum - q_pos)
    count = quantum - q_pos;
  if (copy_from_user(dptr->data[s_pos] + q_pos, buf, count))
  {
    retval = -EFAULT;
    goto out;
  }
  *f_pos += count;
  retval = count;

  if (dev->size < *f_pos)
    dev->size = *f_pos;

out:
  mutex_unlock(&dev->lock);
  return retval;
}

long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
  int retval;

  return retval;
}

loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
  struct scull_dev *dev = filp->private_data;
  loff_t newpos;

  switch (whence)
  {
  case 0: /* SEEK_SET */
    newpos = off;
    break;

  case 1: /* SEEK_CUR */
    newpos = filp->f_pos + off;
    break;

  case 2: /* SEEK_END */
    newpos = dev->size + off;
    break;

  default: /* can't happen */
    return -EINVAL;
  }
  if (newpos < 0)
    return -EINVAL;
  filp->f_pos = newpos;
  return newpos;
}

struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .llseek = scull_llseek,
    .write = scull_write,
    .read = scull_read,
    .unlocked_ioctl = scull_ioctl,
    .open = scull_open,
    .release = scull_release,
};

/**
 * @description: scull设备卸载函数
 * @param {*}
 * @return {*}
 */
void scull_cleanup_module(void)
{
  int i;
  dev_t devno = MKDEV(scull_major, scull_minor);

  if (scull_devices)
  {
    for (i = 0; i < scull_nr_devs; i++)
    {
      scull_trim(scull_devices + i);
      cdev_del(&scull_devices[i].cdev);
    }
    kfree(scull_devices);
  }

  unregister_chrdev_region(devno, scull_nr_devs);

  //scull_p_clean();
  //scull_access_cleanup();
}

/**
 * @description: scull设备注册设备号函数
 * @param {*}
 * @return {*}
 */
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
  int err, devno = MKDEV(scull_major, scull_minor + index);

  cdev_init(&dev->cdev, &scull_fops);
  dev->cdev.owner = THIS_MODULE;
  err = cdev_add(&dev->cdev, devno, 1);

  if (err)
  {
    printk(KERN_NOTICE "Error %d adding scull%d,", err, index);
  }
}

int sucll_init_module(void)
{
  int result, i;
  dev_t dev = 0;

  if (scull_major)
  {
    dev = MKDEV(scull_major, scull_minor);
    result = register_chrdev_region(dev, scull_nr_devs, "scull");
  }
  else
  {
    result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
    scull_major = MAJOR(dev);
  }
  if (result < 0)
  {
    printk(KERN_WARNING "scull:can't get major %d\n", scull_major);
    return result;
  }

  scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
  if (!scull_devices)
  {
    result = -ENOMEM;
    goto fail;
  }
  memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
  for (i = 0; i < scull_nr_devs; i++)
  {
    scull_devices[i].quantum = scull_quantum;
    scull_devices[i].qset = scull_qset;
    mutex_init(&scull_devices[i].lock);
    scull_setup_cdev(&scull_devices[i], i);
  }

  dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
  printk("\r\n init scull \r\nscull_major is %d\r\n", scull_major);
  //dev += scull_p_init(dev);
  //dev += scull_access_init(dev);

fail:
  scull_cleanup_module();
  return result;
}

MODULE_AUTHOR("wyxun023768@icloud.com");
MODULE_LICENSE("Dual BSD/GPL");

module_init(sucll_init_module);
module_exit(scull_cleanup_module);
